#include <Arduino.h>

#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>

#include <LittleFS.h>
#include <ArduinoJson.h>
#include <vector>
#include <cstdint>
#include <OneButton.h>

#include <NTPClient.h>
#include <WiFiUdp.h>

#include <BLEDevice.h>
#include <BLEScan.h>

#define AP_SSID "ESP32C3_Supermini"
#define AP_PASSWORD "12345678@qq.com"
#define WEB_PORT 80
WebServer server(WEB_PORT); // WebServer 对象

// 核心0（CPU0）‌：建议用于网络通信、外设控制等实时性要求高的任务。
// ‌核心1（CPU1）‌：默认运行用户代码（如Arduino的loop()），适合传感器处理或算法运算
// 这里定义使用核心1创建独立任务，用于运行HTTP和按键，防止他们被loop里的delay阻塞
#define CONFIG_ARDUINO_RUNNING_CORE 1

// 板级LED引脚定义
#ifdef BOARD_ESP32C3
#define LED_PIN 13           // 标准ESP32-C3开发板，RST按键一侧的LED引脚
#define LED_ACTIVE_LOW false // 假设标准ESP32-C3是高电平点亮，HIGH点亮，LOW熄灭，如digitalWrite(LED_PIN, HIGH);点亮
#elif defined(BOARD_ESP32C3_SUPERMINI)
#define LED_PIN 8           // SuperMini开发板，RST按键一侧的LED引脚
#define LED_ACTIVE_LOW true // 假设SuperMini是低电平点亮，LOW点亮，HIGH熄灭，如digitalWrite(LED_PIN, LOW);点亮
#else
#error "请在底部状态栏选择环境env:esp32c3或env:esp32c3_supermini。"
#endif

// 为了适应不同的板子，将亮灯、灭灯、闪烁写成函数。灭灯
void ledTurnOn()
{
    digitalWrite(LED_PIN, LED_ACTIVE_LOW ? LOW : HIGH);
}

// 亮灯
void ledTurnOff()
{
    digitalWrite(LED_PIN, LED_ACTIVE_LOW ? HIGH : LOW);
}

// 闪烁，以亮灯开始，灭灯结束
void ledSparkle(int sparkle_num, int sparkle_interval = 250)
{
    if (sparkle_num <= 0)
        return;
    for (int i = 0; i < sparkle_num; i++)
    {
        ledTurnOn();
        delay(sparkle_interval);
        ledTurnOff();
        delay(sparkle_interval);
    }
}

#pragma region 写入和读取配置功能

enum RunMode
{
    NOSET,
    STATIC,
    DYNAMIC_MASTER,
    DYNAMIC_SLAVE
};

inline String runModeToString(RunMode mode)
{
    switch (mode)
    {
    case NOSET:
        return "NOSET";
    case STATIC:
        return "STATIC";
    case DYNAMIC_MASTER:
        return "DYNAMIC_MASTER";
    case DYNAMIC_SLAVE:
        return "DYNAMIC_SLAVE";
    }
}

inline RunMode stringToRunMode(String str)
{
    if (str == "NOSET")
        return NOSET;
    if (str == "STATIC")
        return STATIC;
    if (str == "DYNAMIC_MASTER")
        return DYNAMIC_MASTER;
    if (str == "DYNAMIC_SLAVE")
        return DYNAMIC_SLAVE;
}

struct TimePeriod
{
    uint8_t start_hour;
    uint8_t start_minute;
    uint8_t end_hour;
    uint8_t end_minute;

    String toString() const
    {
        char buf[12];
        snprintf(buf, sizeof(buf), "%02d:%02d-%02d:%02d", start_hour, start_minute, end_hour, end_minute);
        return String(buf);
    }
};

struct Config
{
    RunMode runMode = NOSET; // 运行模式NOSET,STATIC,DYNAMIC_MASTER,DYNAMIC_SLAVE
    uint8_t mac[6] = {0};    // Mac地址，如A8:96:09:BD:3A:BD
    char *raw = nullptr;
    char *ssid = nullptr;
    char *password = nullptr;
    char *appId = nullptr;
    char *appKey = nullptr;
    char *apiUrl = nullptr;
    std::vector<TimePeriod> sendPeriods;

    void clear()
    {
        runMode = NOSET;
        memset(mac, 0, sizeof(mac));
        raw = nullptr;
        ssid = nullptr;
        password = nullptr;
        appId = nullptr;
        appKey = nullptr;
        apiUrl = nullptr;
        sendPeriods.clear();
    }

    ~Config()
    {
        free(raw);
        free(ssid);
        free(password);
        free(appId);
        free(appKey);
        free(apiUrl);
    }

    String toJsonString() const
    {
        DynamicJsonDocument doc(2048);

        doc["runMode"] = runModeToString(runMode);

        char macStr[18];
        snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        doc["mac"] = macStr;

        if (raw)
            doc["raw"] = raw;
        if (ssid)
            doc["ssid"] = ssid;
        if (password)
            doc["password"] = password;
        if (appId)
            doc["appId"] = appId;
        if (appKey)
            doc["appKey"] = appKey;
        if (apiUrl)
            doc["apiUrl"] = apiUrl;

        JsonArray periodsArr = doc.createNestedArray("sendPeriods");
        for (const auto &period : sendPeriods)
        {
            JsonObject p = periodsArr.createNestedObject();
            p["start"] = period.toString().substring(0, 5);
            p["end"] = period.toString().substring(6);
        }

        String output;
        serializeJson(doc, output);
        return output;
    }

    void print() const
    {
        Serial.println("\n===== 当前配置 =====");

        switch (runMode)
        {
        case NOSET:
            Serial.println("未配置");
            break;
        case STATIC:
            Serial.printf("runMode: %s\n", runModeToString(runMode).c_str());
            Serial.printf("mac: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
            Serial.printf("raw: %s\n", raw ? raw : "null");
            break;
        case DYNAMIC_MASTER:
            Serial.printf("runMode: %s\n", runModeToString(runMode).c_str());
            Serial.printf("mac: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
            Serial.printf("ssid: %s\n", ssid ? ssid : "null");
            Serial.printf("password: %s\n", password ? password : "null");
            Serial.printf("appId: %s\n", appId ? appId : "null");
            Serial.printf("appKey: %s\n", appKey ? appKey : "null");
            Serial.printf("apiUrl: %s\n", apiUrl ? apiUrl : "null");
            Serial.println("开启时间段配置:");
            if (sendPeriods.empty())
            {
                Serial.println("  未配置开启时间段");
            }
            else
            {
                for (const auto &period : sendPeriods)
                {
                    Serial.printf("  %s\n", period.toString().c_str());
                }
            }
            break;
        case DYNAMIC_SLAVE:
            Serial.printf("runMode: %s\n", runModeToString(runMode).c_str());
            Serial.printf("mac: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
            Serial.printf("ssid: %s\n", ssid ? ssid : "null");
            Serial.printf("password: %s\n", password ? password : "null");
            Serial.printf("appId: %s\n", appId ? appId : "null");
            Serial.printf("appKey: %s\n", appKey ? appKey : "null");
            Serial.printf("apiUrl: %s\n", apiUrl ? apiUrl : "null");
            break;
        default:
            break;
        }

        Serial.println("====================\n");
    }
};

Config config; // 设备配置

// 保存config到flash，此函数暂时用不上了，因为直接从网页（即handleSave）保存配置到flash的config.json了
// 又能用上了，还好没删。在点击重置设备后，清空config，用此函数将清空的config写入到config.json
void saveConfig(const Config &cfg)
{
    DynamicJsonDocument doc(2048);

    doc["runMode"] = runModeToString(cfg.runMode);

    String temprunMode = runModeToString(cfg.runMode);
    if (temprunMode == "NOSET")
    {
    }
    else if (temprunMode == "STATIC")
    {
        char macStr[18];
        snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", cfg.mac[0], cfg.mac[1], cfg.mac[2], cfg.mac[3], cfg.mac[4], cfg.mac[5]);
        doc["mac"] = macStr;

        if (cfg.raw)
            doc["raw"] = cfg.raw;
    }
    else
    {
        char macStr[18];
        snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", cfg.mac[0], cfg.mac[1], cfg.mac[2], cfg.mac[3], cfg.mac[4], cfg.mac[5]);
        doc["mac"] = macStr;

        if (cfg.ssid)
            doc["ssid"] = cfg.ssid;
        if (cfg.password)
            doc["password"] = cfg.password;
        if (cfg.appId)
            doc["appId"] = cfg.appId;
        if (cfg.appKey)
            doc["appKey"] = cfg.appKey;
        if (cfg.apiUrl)
            doc["apiUrl"] = cfg.apiUrl;

        if (temprunMode == "DYNAMIC_MASTER")
        {
            JsonArray periodsArr = doc.createNestedArray("sendPeriods");
            for (const auto &period : cfg.sendPeriods)
            {
                JsonObject p = periodsArr.createNestedObject();
                p["start"] = period.toString().substring(0, 5);
                p["end"] = period.toString().substring(6);
            }
        }
    }

    File file = LittleFS.open("/config.json", "w");
    if (!file)
    {
        Serial.println("Failed to open config file for writing");
        return;
    }
    serializeJson(doc, file);
    file.close();
}

// 加载flash存储的config配置
void loadConfig(Config &cfg)
{
    File file = LittleFS.open("/config.json", "r");
    if (!file)
    {
        Serial.println("Config file not found");
        return;
    }

    DynamicJsonDocument doc(2048);
    DeserializationError err = deserializeJson(doc, file);
    if (err)
    {
        Serial.print("JSON deserialization failed: ");
        Serial.println(err.c_str());
        return;
    }

    // 清空配置
    cfg.clear();

    // 解析runMode
    if (doc.containsKey("runMode"))
        cfg.runMode = stringToRunMode(doc["runMode"].as<String>());

    if (cfg.runMode == NOSET)
    {
    }
    else if (cfg.runMode == STATIC)
    {
        // 解析MAC地址
        if (doc.containsKey("mac"))
        {
            const char *macStr = doc["mac"];
            sscanf(macStr, "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX", &cfg.mac[0], &cfg.mac[1], &cfg.mac[2], &cfg.mac[3], &cfg.mac[4], &cfg.mac[5]);
        }

        if (doc.containsKey("raw"))
            cfg.raw = strdup(doc["raw"]);
    }
    else
    {
        // 解析MAC地址
        if (doc.containsKey("mac"))
        {
            const char *macStr = doc["mac"];
            sscanf(macStr, "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX", &cfg.mac[0], &cfg.mac[1], &cfg.mac[2], &cfg.mac[3], &cfg.mac[4], &cfg.mac[5]);
        }
        if (doc.containsKey("ssid"))
            cfg.ssid = strdup(doc["ssid"]);
        if (doc.containsKey("password"))
            cfg.password = strdup(doc["password"]);
        if (doc.containsKey("appId"))
            cfg.appId = strdup(doc["appId"]);
        if (doc.containsKey("appKey"))
            cfg.appKey = strdup(doc["appKey"]);
        if (doc.containsKey("apiUrl"))
            cfg.apiUrl = strdup(doc["apiUrl"]);

        if (cfg.runMode == DYNAMIC_MASTER)
        {
            // 解析时间段
            if (doc.containsKey("sendPeriods") && doc["sendPeriods"].is<JsonArray>())
            {
                for (JsonVariant period : doc["sendPeriods"].as<JsonArray>())
                {
                    TimePeriod tp;
                    sscanf(period["start"].as<const char *>(), "%hhu:%hhu", &tp.start_hour, &tp.start_minute);
                    sscanf(period["end"].as<const char *>(), "%hhu:%hhu", &tp.end_hour, &tp.end_minute);
                    cfg.sendPeriods.push_back(tp);
                }
            }
        }
    }

    file.close();
}

void handleRoot()
{
    File file = LittleFS.open("/index.html", "r");
    server.streamFile(file, "text/html");
    file.close();
}

void sendJsonError(int code, const String &message)
{
    server.sendHeader("Content-Type", "application/json");
    server.send(code, "application/json", "{\"error\":\"" + message + "\"}");
}

void handleSave()
{
    /* 这些判断代码都多余了，不需要了
    if (server.method() != HTTP_POST)
    {
        sendJsonError(405, "Method Not Allowed");
        return;
    }

    String contentType = server.header("Content-Type");
    if (contentType.indexOf("application/json") == -1)
    {
      sendJsonError(400, "Invalid Content-Type");
      return;
    }
    Serial.println("contentType为application/json后");

    if (jsonData.length() == 0)
    {
        server.send(400, "text/plain", "Empty request body");
        return;
    }
    */

    String jsonData = server.arg("plain");
    DynamicJsonDocument doc(2048);
    DeserializationError error = deserializeJson(doc, jsonData);
    if (error)
    {
        server.send(400, "text/plain", "Invalid JSON format");
        return;
    }

    File file = LittleFS.open("/config.json", "w");
    if (!file)
    {
        Serial.println("Failed to open config file for writing");
        return;
    }

    serializeJson(doc, file);
    file.close();

    // 保存配置文件后重读配置文件
    loadConfig(config);
    config.print();

    // server.send(200, "application/json", "{\"status\":\"success\"}");
    server.send(200, "text/plain", "配置已保存，正在重启...");
    delay(2000);
    ESP.restart();
}

void handleLoad()
{
    server.send(200, "application/json", config.toJsonString());
}

void handleReset()
{
    // 清空config并将清空后的config保存至配置文件
    config.clear();
    saveConfig(config);

    server.send(200, "text/plain", "设备已重置，正在重启...");
    delay(2000);
    ESP.restart();
}

void handleRestart()
{
    server.send(200, "text/plain", "设备重启中...");
    delay(2000);
    ESP.restart();
}

// 启动AP模式。当什么都没配置时（即第一次上电运行）或动态模式连接不上WiFi时启动AP模式
bool apModeStarted = false;
unsigned long apModeStartTime = 0; // 记录AP开启的时间点
const long apModeTimeout = 600000; // AP模式开启时间。超过设定的时间后重启设备，单位为ms，这里设置10分钟，即10*60*1000

void startAPMode()
{
    if (apModeStarted)
    {
        return;
    }

    WiFi.disconnect(true); // 强制断开WiFi并清除配置
    WiFi.softAP(AP_SSID, AP_PASSWORD);
    Serial.print("AP模式启动，IP地址: ");
    Serial.println(WiFi.softAPIP());

    // 清除所有路由
    server.close();
    server.stop();

    // 重新注册路由
    server.on("/", HTTP_GET, handleRoot);
    server.on("/api/save", HTTP_POST, handleSave);
    server.on("/api/load", HTTP_GET, handleLoad);
    server.on("/api/reset", HTTP_POST, handleReset);
    server.on("/api/restart", HTTP_POST, handleRestart);

    server.begin();
    Serial.println("HTTP服务器已启动");

    ledTurnOn(); // 进入AP模式，状态为常亮

    apModeStartTime = millis();
    apModeStarted = true;
}

// boot按键长按事件
#define BOOT_BUTTON_PIN 9
const unsigned long LONG_PRESS_TIME = 3000; // 长按阈值3秒
OneButton button(BOOT_BUTTON_PIN, true);    // 低电平触发，启用内部上拉

// 长按开始回调
void onLongPressStart()
{
    Serial.println("BOOT按键长按开始事件触发");
    startAPMode();
}

#pragma endregion

// 连接WIFI要用的全局变量
const long intervalConnecting = 250;   // 连接WIFI时灯闪的间隔，单位ms
unsigned long previousMillis = 0;      // 记录上一次灯闪灭的时间
const long connectWifiTimeout = 30000; // 连接WIFI的时间，超过设定的时间，就开启AP。单位ms，这里设定30秒，即30*1000

// 更新蓝牙广播数据
void updateRawAdvertisement(const String &data)
{
    uint8_t bleRaw[31];
    uint8_t bleRaw32[31] = {0};

    // 更新 BLE 广播数据
    int dataLength = data.length() / 2;
    for (int i = 0; i < dataLength; ++i)
    {
        if (i < 31)
        {
            bleRaw[i] = strtoul(data.substring(i * 2, i * 2 + 2).c_str(), nullptr, 16);
        }
        else
        {
            bleRaw32[i - 31] = strtoul(data.substring(i * 2, i * 2 + 2).c_str(), nullptr, 16);
        }
    }

    // 停止当前广播
    esp_ble_gap_stop_advertising();

    // 更新广播数据
    esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw(bleRaw, 31);
    if (errRc != ESP_OK)
    {
        Serial.printf("esp_ble_gap_config_adv_data_raw: %d\n", errRc);
    }

    // 超过31
    if (dataLength > 31)
    {
        esp_ble_gap_config_scan_rsp_data_raw(bleRaw32, sizeof(bleRaw32) / sizeof(bleRaw32[0]));
        if (errRc != ESP_OK)
        {
            Serial.printf("esp_ble_gap_config_scan_rsp_data_raw: %d\n", errRc);
        }
    }

    // 重新启动广播
    esp_ble_adv_params_t adv_params = {
        .adv_int_min = 0x20, // 20ms
        .adv_int_max = 0x30, // 30ms
        .adv_type = ADV_TYPE_SCAN_IND,
        .channel_map = ADV_CHNL_ALL};
    esp_ble_gap_start_advertising(&adv_params);
}

// 请求LeanCloud的变量
const String className = "/1.1/classes/AdvertisingData"; // Class不需要动

// 动态从机模式需要用的全局变量
unsigned long lastUpdateMillis = 0;       // 记录上一次更新蓝牙raw的时间
const long updateInterval = 10000;        // 更新蓝牙raw的间隔，即动态从机模式下，多长时间向LeanCloud获取一次蓝牙raw。单位ms，这里设定10s更新一次
unsigned long firstMillis;                // 全局变量保存首次时间戳
const long dynamicSlaveTimeout = 1200000; // 动态从机模式下，多长时间重启。单位ms，这里设定20分钟，即20*60*1000

// 将uint8_t数组类型的Mac地址转为String，便于上传到LeanCloud数据库
String uint8_tMacToHexString(uint8_t mac[6])
{
    char macStr[18]; // MAC字符串缓冲区（6字节x2 + 5个冒号 + 终止符）
    sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    String result = String(macStr);
    result.toUpperCase(); // 统一转为大写
    return result;
}

// 将uint8_t*类型的raw转为String，便于上传到LeanCloud数据库时查询指定mac的行记录，方法与Mac转String类似，只是一个是知道长度，一个用for循环
String vectorRawToHexString(const std::vector<uint8_t> &data)
{
    String rawStr;
    for (auto byte : data)
    {
        if (byte < 0x10)
            rawStr += "0"; // 补零
        rawStr += String(byte, HEX);
    }
    rawStr.toUpperCase(); // 统一大写
    return rawStr;
}

// 获取LeanCloud蓝牙广播数据
void fetchAdvertisingData()
{
    if ((WiFi.status() == WL_CONNECTED))
    {
        String serverUrl = String(config.apiUrl) + className;
        HTTPClient http;
        String queryUrl = serverUrl + "?where={\"mac\":\"" + uint8_tMacToHexString(config.mac) + "\"}"; // 先查询是否已存在该mac记录
        http.begin(queryUrl);
        http.addHeader("Content-Type", "application/json");
        http.addHeader("X-LC-Id", config.appId);
        http.addHeader("X-LC-Key", config.appKey);
        http.addHeader("User-Agent", "ESP32C3Supermini/1.0");
        int httpResponseCode = http.GET();

        if (httpResponseCode > 0)
        {
            String payload = http.getString();
            DynamicJsonDocument queryDoc(1024); // 解析 JSON 数据
            deserializeJson(queryDoc, payload);
            if (queryDoc["results"].size() > 0)
            {
                String data = queryDoc["results"][0]["data"];
                Serial.printf("从云端查询到记录：Mac：%s，Raw：%s\n", uint8_tMacToHexString(config.mac).c_str(), data.c_str());
                updateRawAdvertisement(data);
            }
            else
            {
                Serial.println("未从云端查询到记录");
            }
        }

        http.end();
    }
}

// 动态主机模式需要用的全局变量
int httpFailCount = 0;
const int maxHttpFailCount = 3;
std::vector<uint8_t> lastPayload; // 动态主机模式下，存储指定Mac地址设备上一次的raw数据

// NTP服务器配置
const char *ntpServer = "ntp1.aliyun.com";
const long gmtOffset = 8 * 3600; // GMT+8时区(北京时间)
const int daylightOffset = 0;    // 不使用夏令时
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, gmtOffset, daylightOffset);
tm timeInfo;
bool RTCSynchronized = false; // 记录此次开机，设备RTC是否同步过网络时间

// 同步网络时间至设备内部RTC时间
void syncRTC()
{
    if (WiFi.status() == WL_CONNECTED && timeClient.update())
    {
        Serial.println("NTP time updated");
        time_t epochTime = timeClient.getEpochTime();
        localtime_r(&epochTime, &timeInfo);

        // 设置ESP32内部RTC，即同步网络时间至设备内部RTC
        struct timeval tv = {epochTime, 0};
        settimeofday(&tv, NULL);
        Serial.println("RTC synchronized");
        RTCSynchronized = true; // 标记此次开机，设备RTC已经同步过网络时间
    }
    else
    {
        Serial.println("Failed to get NTP time");
    }
}

// 判断当前时间是否在设定的时间内，如未设定，则直接返回true。
bool isTimeInRange(const tm &timeInfo, const std::vector<TimePeriod> &sendPeriods)
{
    bool istimeinrange = false;

    // 判断vector是否为空
    if (sendPeriods.empty())
    {
        istimeinrange = true;
    }
    else
    {
        int currentTimeInMinutes = timeInfo.tm_hour * 60 + timeInfo.tm_min;

        for (const auto &period : sendPeriods)
        {
            int startTimeInMinutes = period.start_hour * 60 + period.start_minute;
            int endTimeInMinutes = period.end_hour * 60 + period.end_minute;

            if (startTimeInMinutes <= endTimeInMinutes)
            { // 如果开始时间早于结束时间（常规情况）
                istimeinrange = currentTimeInMinutes >= startTimeInMinutes && currentTimeInMinutes <= endTimeInMinutes;
            }
            else
            { // 如果开始时间晚于结束时间（跨越午夜）
                istimeinrange = currentTimeInMinutes >= startTimeInMinutes || currentTimeInMinutes <= endTimeInMinutes;
            }

            if (istimeinrange)
            {
                return istimeinrange;
            }
        }
    }

    return istimeinrange;
}

// 扫描设备回调函数
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
public:
    // std::vector 是 C++ 标准模板库（STL）中的一个动态数组，提供了可以动态增长和缩小的数组功能。当 std::vector 用于存储 uint8_t 类型数据时，它通常用于处理原始字节数据，如文件读写、网络数据传输等场景。每个元素都是一个 8 位的无符号整数，表示从 0 到 255 的数值。
    std::vector<uint8_t> currentPayload;
    bool hasChanged = false;
    bool hasFoundTargetDevice = false;

    void onResult(BLEAdvertisedDevice advertisedDevice)
    {
        // MAC地址过滤，当找到的是非指定Mac地址的设备，打印后return
        if (memcmp(config.mac, "\x00\x00\x00\x00\x00\x00", 6) != 0)
        {
            if (memcmp(advertisedDevice.getAddress().getNative(), config.mac, 6) != 0)
            {
                Serial.print("    找到非目标设备：");
                Serial.println(advertisedDevice.getAddress().toString().c_str());
                return;
            }
        }

        hasFoundTargetDevice = true;
        Serial.print("    找到指定目标设备：");
        Serial.println(advertisedDevice.toString().c_str());

        // 获取原始通告数据
        uint8_t *payload = advertisedDevice.getPayload();
        size_t payloadLength = advertisedDevice.getPayloadLength();
        currentPayload.assign(payload, payload + payloadLength);

        // 比较当前数据和上次数据是否变化
        if (currentPayload != lastPayload)
        {
            // 更新上次数据
            lastPayload = currentPayload;
            hasChanged = true;
        }
        else
        {
            hasChanged = false;
        }
    }
};
MyAdvertisedDeviceCallbacks advertisedDeviceCallbacks; // 定义全局的 MyAdvertisedDeviceCallbacks

// 上传最新的Raw数据，查询mac，有就更新，没有就新增。该函数已被弃用，因为是先查询有没有记录，再进行更新或新增，即每次都要向LeanCloud进行两次请求，浪费请求次数，故改用下面的重载函数。
void updateOrCreateDataA(bool &isHttpRequestSuccess, int &httpResponseCode, char *&info, char *mac, char *raw)
{
    // 每次begin后必须加http.addHeader("Content-Type", "application/json");http.addHeader("X-LC-Id", appId);http.addHeader("X-LC-Key", appKey);http.addHeader("User-Agent", "ESP32C3Supermini/1.0");
    String serverUrl = String(config.apiUrl) + className;
    HTTPClient http;

    // 先查询是否已存在该mac记录
    String queryUrl = serverUrl + "?where={\"mac\":\"" + String(mac) + "\"}";
    http.begin(queryUrl);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("X-LC-Id", config.appId);
    http.addHeader("X-LC-Key", config.appKey);
    http.addHeader("User-Agent", "ESP32C3Supermini/1.0");
    httpResponseCode = http.GET();
    if (httpResponseCode > 0)
    {
        isHttpRequestSuccess = true;
        if (httpResponseCode == HTTP_CODE_OK)
        {
            DynamicJsonDocument queryDoc(1024);
            deserializeJson(queryDoc, http.getString());
            if (queryDoc["results"].size() > 0)
            {
                // 存在则更新
                String objectId = queryDoc["results"][0]["objectId"];
                String updateUrl = serverUrl + "/" + objectId;
                http.begin(updateUrl);
                http.addHeader("Content-Type", "application/json");
                http.addHeader("X-LC-Id", config.appId);
                http.addHeader("X-LC-Key", config.appKey);
                http.addHeader("User-Agent", "ESP32C3Supermini/1.0");

                DynamicJsonDocument updateDoc(256);
                updateDoc["data"] = raw;
                String payload;
                serializeJson(updateDoc, payload);
                httpResponseCode = http.PUT(payload);
                if (httpResponseCode > 0)
                {
                    isHttpRequestSuccess = true;
                    if (httpResponseCode == HTTP_CODE_OK)
                        info = "向LeanCloud更新，更新Raw成功";
                    else
                        info = "向LeanCloud更新，更新Raw失败";
                }
                else
                {
                    isHttpRequestSuccess = false;
                    info = "向LeanCloud更新，HTTP请求失败";
                }
            }
            else
            {
                // 不存在则新增
                http.begin(serverUrl);
                http.addHeader("Content-Type", "application/json");
                http.addHeader("X-LC-Id", config.appId);
                http.addHeader("X-LC-Key", config.appKey);
                http.addHeader("User-Agent", "ESP32C3Supermini/1.0");

                DynamicJsonDocument newDoc(256);
                newDoc["mac"] = mac;
                newDoc["data"] = raw;

                // 必须加下面3句ACL，显式设置ACL：允许所有人读，所有人写
                JsonObject acl = newDoc.createNestedObject("ACL");
                acl["*"]["read"] = true;
                acl["*"]["write"] = true;

                String payload;
                serializeJson(newDoc, payload);
                httpResponseCode = http.POST(payload);
                if (httpResponseCode > 0)
                {
                    isHttpRequestSuccess = true;
                    if (httpResponseCode == HTTP_CODE_CREATED)
                        info = "向LeanCloud新增，新增Mac和Raw数据成功";
                    else
                        info = "向LeanCloud新增，新增Mac和Raw数据失败";
                }
                else
                {
                    isHttpRequestSuccess = false;
                    info = "向LeanCloud新增，HTTP请求失败";
                }
            }
        }
        else
        {
            isHttpRequestSuccess = true;
            info = "向LeanCloud查询，查询失败";
        }
    }
    else
    {
        isHttpRequestSuccess = false;
        info = "向LeanCloud查询，HTTP请求失败";
    }
    http.end();
}

String objectId = ""; // 动态主机模式下，更新需要向LeanCloud两次请求，第一次请求，获取指定mac行记录的objectId，第二次根据objectId请求更新，为了减少请求次数，将objectId临时缓存
enum QueryInfo        // 向LeanCloud发送查询请求的结果信息
{
    ERROR,                    // 查询出错
    FOUND,                    // 查询找到行记录
    NO_QUERY_CACHED_OBJECTID, // 未执行查询，因为已经缓存过objectId
    NOTFOUND                  // 查询未找到行记录
};
// 上传最新的Raw数据，将objectId缓存至程序，减少向LeanCloud请求次数
void updateOrCreateData(bool &isHttpRequestSuccess, int &httpResponseCode, char *&info, char *mac, char *raw)
{
    QueryInfo queryInfo = ERROR;
    String serverUrl = String(config.apiUrl) + className;
    HTTPClient http; // 每次begin后必须加http.addHeader这几句请求头

    // Serial.printf("%s\n", objectId == "" ? "objectId为空" : objectId.c_str());
    if (objectId == "") // 如本没有objectId临时缓存，就查询objectId
    {
        String queryUrl = serverUrl + "?where={\"mac\":\"" + String(mac) + "\"}";
        http.begin(queryUrl);
        http.addHeader("Content-Type", "application/json");
        http.addHeader("X-LC-Id", config.appId);
        http.addHeader("X-LC-Key", config.appKey);
        http.addHeader("User-Agent", "ESP32C3Supermini/1.0");
        httpResponseCode = http.GET();
        if (httpResponseCode > 0)
        {
            isHttpRequestSuccess = true;
            if (httpResponseCode == HTTP_CODE_OK)
            {
                // info = "向LeanCloud查询请求，请求成功";
                // Serial.printf("%s，%s\n", info, queryUrl.c_str());
                DynamicJsonDocument queryDoc(1024);
                deserializeJson(queryDoc, http.getString());
                if (queryDoc["results"].size() > 0)
                {
                    queryInfo = FOUND;
                    objectId = strdup(queryDoc["results"][0]["objectId"]); // 将查询到的mac行记录的objectId缓存至本地
                    // Serial.printf("查询到行记录：%s\n", objectId.c_str());
                }
                else
                {
                    queryInfo = NOTFOUND;
                    // Serial.printf("未查询到行记录\n");
                }
            }
            else
            {
                info = "向LeanCloud查询，查询失败";
                // Serial.printf("%s\n", info);
            }
        }
        else
        {
            info = "向LeanCloud查询，HTTP请求失败";
        }
        http.end();
    }
    else // 已有objectId临时缓存
    {
        queryInfo = NO_QUERY_CACHED_OBJECTID;
        // Serial.printf("已有本地缓存objectId：%s\n", objectId.c_str());
    }

    if (queryInfo == FOUND || queryInfo == NO_QUERY_CACHED_OBJECTID) // 查询到行记录或者已有objectId临时缓存，执行更新
    {
        String updateUrl = serverUrl + "/" + objectId;
        // Serial.printf("%s\n", updateUrl.c_str());
        http.begin(updateUrl);
        http.addHeader("Content-Type", "application/json");
        http.addHeader("X-LC-Id", config.appId);
        http.addHeader("X-LC-Key", config.appKey);
        http.addHeader("User-Agent", "ESP32C3Supermini/1.0");

        DynamicJsonDocument updateDoc(256);
        updateDoc["data"] = raw;
        String updateStr;
        serializeJson(updateDoc, updateStr);
        httpResponseCode = http.PUT(updateStr);
        if (httpResponseCode > 0)
        {
            isHttpRequestSuccess = true;
            // info = "向LeanCloud更新请求，请求成功";
            // Serial.printf("%s，%s\n", info, updateUrl.c_str());
            if (httpResponseCode == HTTP_CODE_OK)
                info = "向LeanCloud更新，更新Raw成功";
            else
                info = "向LeanCloud更新，更新Raw失败";
            // Serial.printf("%s\n", info);
        }
        else
        {
            isHttpRequestSuccess = false;
            info = "向LeanCloud更新，HTTP请求失败";
            // Serial.printf("%s\n", info);
        }
    }
    else if (queryInfo == NOTFOUND) // 查到行记录为空，执行新增
    {
        // 不存在则新增
        http.begin(serverUrl);
        http.addHeader("Content-Type", "application/json");
        http.addHeader("X-LC-Id", config.appId);
        http.addHeader("X-LC-Key", config.appKey);
        http.addHeader("User-Agent", "ESP32C3Supermini/1.0");

        DynamicJsonDocument createDoc(256);
        createDoc["mac"] = mac;
        createDoc["data"] = raw;

        // 必须加下面3句ACL，显式设置ACL：允许所有人读，所有人写
        JsonObject acl = createDoc.createNestedObject("ACL");
        acl["*"]["read"] = true;
        acl["*"]["write"] = true;

        String createStr;
        serializeJson(createDoc, createStr);
        httpResponseCode = http.POST(createStr);
        if (httpResponseCode > 0)
        {
            isHttpRequestSuccess = true;
            // info = "向LeanCloud新增请求，请求成功";
            // Serial.printf("%s，%s\n", info, serverUrl.c_str());
            if (httpResponseCode == HTTP_CODE_CREATED)
                info = "向LeanCloud新增，新增Mac和Raw数据成功";
            else
                info = "向LeanCloud新增，新增Mac和Raw数据失败";
            // Serial.printf("%s\n", info);
        }
        else
        {
            isHttpRequestSuccess = false;
            info = "向LeanCloud新增，HTTP请求失败";
            // Serial.printf("%s\n", info);
        }
    }
    else // 查询失败
    {
        // Serial.printf("查询失败\n");
    }
}

void setup()
{
    Serial.begin(115200);
    firstMillis = millis(); // 初始化记录首次时间戳

    pinMode(LED_PIN, OUTPUT);
    ledTurnOff(); // 灭灯

    pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP);
    button.attachLongPressStart(onLongPressStart); // 注册长按事件
    button.setPressMs(LONG_PRESS_TIME);            // 设置长按阈值

    delay(2000);
    Serial.println("设备启动");

    time_t now;
    time(&now);
    localtime_r(&now, &timeInfo); // 线程安全的时间转换，将设备RTC时间读到timeInfo
    // 打印本地时间
    char timeStr[64];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeInfo);
    Serial.printf("Unsynchronized RTC, current RTC time: %s\n", timeStr);

    if (!LittleFS.begin(true))
    {
        Serial.println("LittleFS启动失败，2秒后自动重启设备");
        delay(2000);
        ESP.restart();
    }

    if (!LittleFS.exists("/index.html"))
    {
        Serial.println("未找到index.html");
    }

    if (!LittleFS.exists("/config.json"))
    {
        File file = LittleFS.open("/config.json", "w");
        if (!file)
        {
            Serial.println("创建配置文件失败，2秒后自动重启设备");
            delay(2000);
            ESP.restart();
        }
        file.close();
    }

    // 加载并打印config
    loadConfig(config);
    config.print();

    /*xTaskCreatePinnedToCore函数原型
    xTaskCreatePinnedToCore(
    TaskFunction_t pvTaskCode,    // 任务函数指针
    const char *const pcName,     // 任务名称（最大16字符）
    const uint32_t usStackDepth,  // 堆栈大小（字节）
    void *const pvParameters,     // 任务参数
    UBaseType_t uxPriority,       // 优先级（0最低）
    TaskHandle_t *const pvCreatedTask, // 任务句柄
    const BaseType_t xCoreID      // 核心编号（0或1）
    );
    */
    // 在setup()中创建独立任务，用于循环调用网络和按键任务，防止loop里的delay阻塞网络和按键。
    /*且loop循环内如果有循环的话，循环里面加上
    if (apModeStarted)
    {
        ledTurnOn();
        return;
    }
    */
    xTaskCreatePinnedToCore(
        [](void *)
        {
            for (;;)
            {
                server.handleClient(); // 专用网络任务，必须循环调用，开启AP，监听请求
                button.tick();         // 专用按键任务，必须定期调用以处理按键事件
            }
        },
        "HTTP_AND_BUTTON", 4096, NULL, 1, NULL, CONFIG_ARDUINO_RUNNING_CORE);

    // 未配置功能
    if (config.runMode == NOSET)
    {
        Serial.println("设备无配置，开启AP模式");
        startAPMode(); // 开启AP模式，进入配置页面HTML
        return;
    }

    // 如果是静态模式和动态从机模式，需要修改mac地址
    if (config.runMode == STATIC || config.runMode == DYNAMIC_SLAVE)
    {
        // esp32没有提供设置蓝牙mac地址的api 通过查看esp32的源代码
        // 此操作将根据蓝牙mac算出base mac
        uint8_t temp_mac[6];
        memcpy(temp_mac, config.mac, sizeof(config.mac));
        if (UNIVERSAL_MAC_ADDR_NUM == FOUR_UNIVERSAL_MAC_ADDR)
        {
            temp_mac[5] -= 2;
        }
        else if (UNIVERSAL_MAC_ADDR_NUM == TWO_UNIVERSAL_MAC_ADDR)
        {
            temp_mac[5] -= 1;
        }
        esp_base_mac_addr_set(temp_mac);
    }

    // 初始化
    BLEDevice::init("");

    // 静态模式功能
    if (config.runMode == STATIC)
    {
        updateRawAdvertisement(config.raw); // 静态模式，直接广播设定好的raw数据
        return;
    }

    // 尝试30s的连接WiFi
    WiFi.begin(config.ssid, config.password);
    Serial.print("正在连接WiFi...");
    unsigned long connectionStartTime = millis();
    while (WiFi.status() != WL_CONNECTED)
    {
        if (apModeStarted)
        {
            ledTurnOn();
            return;
        }

        delay(1000);
        Serial.print(".");

        // 间隔闪烁
        unsigned long currentMillis = millis();
        if (currentMillis - previousMillis >= intervalConnecting)
        {
            previousMillis = currentMillis;
            digitalWrite(LED_PIN, !digitalRead(LED_PIN));
        }

        // 超过30秒未连接，结束尝试，后续在loop循环时再尝试连接
        if (millis() - connectionStartTime >= connectWifiTimeout)
        {
            Serial.println("setup中连接WiFi超时，后续在loop循环中尝试连接");
            break;
        }
    }

    if (WiFi.status() == WL_CONNECTED)
    {
        Serial.println("\nWiFi已连接");
    }

    ledTurnOff(); // 熄灭 LED

    // 动态主机模式功能
    if (config.runMode == DYNAMIC_MASTER)
    {
        BLEScan *pBLEScan = BLEDevice::getScan();
        pBLEScan->setAdvertisedDeviceCallbacks(&advertisedDeviceCallbacks);
        pBLEScan->setActiveScan(true); // 主动扫描
        pBLEScan->setInterval(100);
        pBLEScan->setWindow(99); // 扫描间隔和窗口设置

        timeClient.begin();
        timeClient.setUpdateInterval(3600000); // 每小时更新一次
        syncRTC();                             // 首次时间同步
    }
    else // 动态从机模式功能
    {
        // 从LeanCloud获取最新的raw并更新广播数据
        fetchAdvertisingData();
    }
}

void loop()
{
    if (apModeStarted)
    {
        int apModeRunTime = millis() - apModeStartTime;
        Serial.printf("设定AP模式运行10分钟（600秒）后重启设备，当前已运行%d秒\n", apModeRunTime / 1000);
        if (apModeRunTime >= apModeTimeout)
        { // AP 模式运行设定的时间后重启
            Serial.println("AP模式超时，重启设备");
            delay(2000);
            ESP.restart();
        }

        delay(10000); // 适当延时减少CPU占用
        return;
    }

    // 未配置功能或静态模式运行，啥也不用干
    if (config.runMode == NOSET || config.runMode == STATIC)
    {
        // Serial.printf("当前运行模式为%s，loop循环中无需运行功能\n",runModeToString(config.runMode).c_str());
        delay(10000); // 适当延时减少CPU占用
        return;
    }

    // 动态模式loop循环内如果WIFI没连接，动态从机模式，判断时间进入AP；动态主机模式，就一直尝试连接，直到连接上为止，是为了防止连接的WIFI断电了，又通电了
    if (WiFi.status() != WL_CONNECTED)
    {
        // 尝试连接到 WiFi
        WiFi.begin(config.ssid, config.password);
        unsigned long connectionStartTime = millis();
        Serial.print("正在连接WiFi...");
        while (WiFi.status() != WL_CONNECTED)
        {
            if (apModeStarted)
            {
                ledTurnOn();
                return;
            }

            delay(1000);
            Serial.print(".");

            // 间隔闪烁
            unsigned long currentMillis = millis();
            if (currentMillis - previousMillis >= intervalConnecting)
            {
                previousMillis = currentMillis;
                digitalWrite(LED_PIN, !digitalRead(LED_PIN));
            }

            // 如果超过 30 秒未连接
            if (millis() - connectionStartTime >= connectWifiTimeout)
            {
                // 如果是动态主机模式且超过 30 秒未连接，结束此次loop循环，进入下一次loop循环再尝试连接
                if (config.runMode == DYNAMIC_MASTER)
                {
                    Serial.println("loop中连接WiFi超时，在下一次loop循环中再尝试连接");
                    return;
                }
                else if (config.runMode == DYNAMIC_SLAVE)
                {
                    Serial.println("连接WiFi超时，启动AP模式");
                    startAPMode(); // 开启AP模式，进入配置页面HTML
                    return;
                }
            }
        }
    }

    // 程序能走到这一步，一定是已经连接上WIFI了
    Serial.println("\nWiFi已连接");
    ledTurnOff(); // 熄灭 LED

    static unsigned long lastSync = 0;
    if (!RTCSynchronized || millis() - lastSync >= 3600000) // 如果此次开机设备还未同步过网络时间 或 每过1个小时，开始同步网络时间
    {
        syncRTC();
        lastSync = millis();
    }

    if (config.runMode == DYNAMIC_MASTER) // 动态主机模式运行
    {
        // 读取设备内部RTC时间，前面处理逻辑里已经至少同步过一次网络时间了
        time_t now;
        time(&now);
        localtime_r(&now, &timeInfo); // 线程安全的时间转换，将设备RTC时间读到timeInfo
        // 打印本地时间
        char timeStr[64];
        strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeInfo);
        Serial.printf("Current RTC time: %s\n", timeStr);

        // 时间段判断逻辑，即只在设置的时间段内更新蓝牙raw到数据库
        if (!isTimeInRange(timeInfo, config.sendPeriods)) // 如果不在设定的时间范围内
        {
            Serial.println("当前时间不在指定时间段内");
            delay(10000); // 延迟10秒，跳出该次loop循环
            return;
        }
        Serial.println("当前时间在指定时间段内");

        Serial.println("开始扫描。。。");
        BLEScanResults foundDevices = BLEDevice::getScan()->start(10);
        Serial.println("本次扫描结束！");
        if (advertisedDeviceCallbacks.hasFoundTargetDevice)
        {
            Serial.print("扫描发现目标主机，");
            advertisedDeviceCallbacks.hasFoundTargetDevice = false; // 重置判断一轮扫描是否找到指定Mac设备的变量，以便于判断新一轮扫描是否找到指定Mac地址的设备

            if (advertisedDeviceCallbacks.hasChanged)
            {
                Serial.println("目标主机Raw数据发生变化");
                // 传出来的参数
                bool isHttpRequestSuccess;
                int httpResponseCode;
                char *info = "";

                // 传进去的参数
                char *macCharx = strdup(uint8_tMacToHexString(config.mac).c_str());
                char *rawCharx = strdup(vectorRawToHexString(advertisedDeviceCallbacks.currentPayload).c_str());
                Serial.printf("上传变化后的Raw数据：%s\n", rawCharx);
                updateOrCreateData(isHttpRequestSuccess, httpResponseCode, info, macCharx, rawCharx);
                if (isHttpRequestSuccess)
                {
                    httpFailCount = 0;
                    Serial.printf("HTTP响应代码：%d\n提示信息：%s\n", httpResponseCode, info);
                }
                else
                {
                    httpFailCount++;
                }
                if (httpFailCount >= maxHttpFailCount)
                {
                    Serial.println("HTTP请求连续失败三次，重启设备");
                    delay(2000);
                    ESP.restart();
                }

                // 在发送数据时闪烁两下
                ledSparkle(2);
            }
            else
            {
                Serial.println("目标主机Raw数据未发生变化");
            }
        }
        else
        {
            Serial.print("扫描未发现发现目标主机，");
        }

        Serial.print("倒计时10s后进行下一轮扫描：");
        for (int i = 10; i > 0; i--)
        {
            if (apModeStarted)
            {
                ledTurnOn();
                return;
            }

            Serial.printf("%d ", i);
            delay(1000);
        }
        Serial.println("0");
        BLEDevice::getScan()->clearResults(); // 清空缓存
    }
    else if (config.runMode == DYNAMIC_SLAVE) // 动态从机模式下运行
    {
        // 当超过设定的更新蓝牙raw的间隔，就从LeanCloud获取raw，更新一次raw
        unsigned long currentMillis = millis();
        if (currentMillis - lastUpdateMillis >= updateInterval)
        {
            lastUpdateMillis = currentMillis;
            fetchAdvertisingData();
        }

        // 每30分钟重启一次
        unsigned long elapsedMillis = currentMillis - firstMillis; // 计算经过的毫秒数

        // 打印设备运行时间代码，可以删除
        unsigned long totalSeconds = elapsedMillis / 1000; // 总秒数
        unsigned int minutes = totalSeconds / 60;          // 取整数分钟
        unsigned int seconds = totalSeconds % 60;          // 取剩余秒数
        Serial.printf("设备设定运行%d分%d秒重启，", dynamicSlaveTimeout / 1000 / 60, dynamicSlaveTimeout / 1000 % 60);
        Serial.print("当前已运行");
        if (minutes > 0)
        {
            Serial.printf("%d分", minutes);
        }
        Serial.printf("%d秒\n", seconds);

        if (elapsedMillis >= dynamicSlaveTimeout)
        {
            Serial.println("动态从机模式下设备运行超时，2秒后自动重启设备");
            delay(2000);
            ESP.restart();
        }

        delay(5000); // 适当延时减少CPU占用
    }
}
